package com.hippo.core.service;

import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson2.JSON;
import com.hippo.core.dao.CaseDao;
import com.hippo.core.dao.RecordDao;
import com.hippo.core.util.*;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.mock.web.MockMultipartFile;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.net.URLEncoder;
import java.text.SimpleDateFormat;
import java.util.*;

/**
 * @author zhuzhanhao
 */
@Service
@Slf4j
public class CaseService {

    @Autowired
    private CaseDao caseDao;

    @Autowired
    private QiniuUtils qiniuUtils;

    @Autowired
    private RecordDao recordDao;

    public JSONObject getCaseList(JSONObject obj) {
        CommonUtil.fillPageParam(obj);
        int count = caseDao.countCase(obj);
        List<JSONObject> list = caseDao.getCaseList(obj);
        return CommonUtil.successPage(obj, list, count);
    }

    public JSONObject addCase(JSONObject obj) {
        JSONArray step = obj.getJSONArray("steps");
        JSONArray jsonArray = new JSONArray();
        step.forEach(item -> {
            JSONObject jsonObject = JSONObject.parseObject(JSON.toJSONString(item));
            if (jsonObject.getString("caseName") == null & jsonObject.getString("caseId") == null) {
                jsonObject.put("caseName", obj.getString("name"));
            }
            jsonArray.add(jsonObject);
        });
        // 更新自己的用例信息
        JSONArray referenceCaseId = StringTools.caseIdRecursion(new JSONArray(), jsonArray);
        referenceCaseId.remove(obj.getString("id"));
        obj.put("referenceCaseId", JSON.toJSONString(referenceCaseId));
        obj.put("isReference", 2); //是否被引用，新建用例不可能被引用，默认赋值2
        obj.put("steps", JSON.toJSONString(jsonArray));
        return CommonUtil.response(caseDao.addCase(obj));
    }


    public JSONObject updateCase(JSONObject obj) {
        JSONArray step = obj.getJSONArray("steps");
        JSONArray jsonArray = new JSONArray();
        step.forEach(item -> {
            JSONObject jsonObject = JSONObject.parseObject(JSON.toJSONString(item));
            if (jsonObject.getString("caseName") != null) {
                if (!jsonObject.getString("caseName").equals(obj.getString("name")) & jsonObject.getJSONArray("children").size() == 0) {
                    jsonObject.put("caseName", obj.getString("name"));
                }
            }
            jsonArray.add(jsonObject);
        });
        // 如果当前用例有被引用，需要将当前用例下父节点为0的改为有当前用例id
        JSONArray jsonArray1 = new JSONArray();
        if (obj.getInteger("isReference") == 1) {
            JSONArray finalJsonArray = jsonArray1;
            jsonArray.forEach(item -> {
                JSONObject jsonObject = JSONObject.parseObject(JSON.toJSONString(item));
                if (jsonObject.getString("parent_id").equals("0")) {
                    jsonObject.put("parent_id", obj.getString("id"));
                }
                finalJsonArray.add(jsonObject);
            });
        } else {
            jsonArray1 = jsonArray;
        }
        JSONArray referenceCaseId = StringTools.caseIdRecursion(new JSONArray(), jsonArray1);
        referenceCaseId.remove(obj.getString("id"));
        obj.put("referenceCaseId", JSON.toJSONString(referenceCaseId));
        // 查询哪些用例引用了当前用例
        List<JSONObject> referenceIds = caseDao.getReferenceIds(obj);
        obj.put("isReference", referenceIds.size() > 0 ? 1 : 2); // 被引用数量大于1，则是否引用插入1
        // 如果引用的数量大于0，那么更新所有引用当前用例的steps字段值
        for (int i = 0; i < referenceIds.size(); i++) {
            JSONArray haveReferenceSteps = referenceIds.get(i).getJSONArray("steps");
            JSONArray haveReferenceNewSteps = new JSONArray();
            for (int j = 0; j < haveReferenceSteps.size(); j++) {
                JSONObject jsonObject = JSONObject.parseObject(JSON.toJSONString(haveReferenceSteps.get(j)));
                if (jsonObject.getJSONArray("children").size() > 0) {
                    if (jsonObject.getString("caseId").equals(obj.getString("id"))) {
                        jsonObject.put("children", jsonArray1);
                        jsonObject.put("stepName", obj.getString("name"));
                    }
                }
                haveReferenceNewSteps.add(jsonObject);
            }
            JSONObject jsonObject = new JSONObject();
            jsonObject.put("steps", JSON.toJSONString(haveReferenceNewSteps));
            jsonObject.put("id", referenceIds.get(i).getString("id"));
            caseDao.updateSteps(jsonObject);
        }
        obj.put("steps", JSON.toJSONString(jsonArray));
        return CommonUtil.response(caseDao.updateCase(obj));
    }

    public JSONObject deleteCase(String id) {
        return CommonUtil.response(caseDao.deleteCase(id));
    }

    public JSONObject getOperationList() {
        List<JSONObject> operationList = caseDao.getOperationList();
        ArrayList<Object> arrayList = new ArrayList<>();
        ArrayList<Object> arrayList1 = new ArrayList<>();
        ArrayList<Object> arrayList2 = new ArrayList<>();
        ArrayList<Object> arrayList3 = new ArrayList<>();
        ArrayList<Object> arrayList4 = new ArrayList<>();
        ArrayList<Object> arrayList5 = new ArrayList<>();
        operationList.forEach(obj -> {
            JSONObject map = new JSONObject(true);
            map.put("value", obj.getString("id"));
            map.put("label", obj.getString("name"));
            map.put("element", obj.getString("element"));
            map.put("data", obj.getString("data"));
            if (obj.getString("type").equals("browser")) {
                arrayList1.add(map);
            } else if (obj.getString("type").equals("element")) {
                arrayList2.add(map);
            } else if (obj.getString("type").equals("mouse")) {
                arrayList3.add(map);
            } else if (obj.getString("type").equals("keyboard")) {
                arrayList4.add(map);
            } else if (obj.getString("type").equals("assert")) {
                arrayList5.add(map);
            }
        });
        arrayList.add(arrayList1);
        arrayList.add(arrayList2);
        arrayList.add(arrayList3);
        arrayList.add(arrayList4);
        arrayList.add(arrayList5);
        String[] labelArray = {"浏览器操作", "元素操作", "鼠标操作", "输入操作", "断言操作"};
        String[] valueArray = {"browser", "element", "mouse", "keyboard", "assert"};
        ArrayList<Object> resList = new ArrayList<>();
        for (int i = 0; i < labelArray.length; i++) {
            HashMap<String, Object> map = new HashMap<>();
            map.put("label", labelArray[i]);
            map.put("value", valueArray[i]);
            map.put("children", arrayList.get(i));
            resList.add(map);
        }
        return CommonUtil.successJson(resList);
    }

    public HashMap<String, String> operationDict() {
        HashMap<String, String> map = new HashMap<>();
        List<JSONObject> operationList = caseDao.getOperationList();
        for (int i = 0; i < operationList.size(); i++) {
            switch (operationList.get(i).getString("type")) {
                case "browser":
                    map.put(operationList.get(i).getString("id"), "浏览器操作");
                    break;
                case "element":
                    map.put(operationList.get(i).getString("id"), "元素操作");
                    break;
                case "mouse":
                    map.put(operationList.get(i).getString("id"), "鼠标操作");
                    break;
                case "keyboard":
                    map.put(operationList.get(i).getString("id"), "输入操作");
                    break;
                case "assert":
                    map.put(operationList.get(i).getString("id"), "断言操作");
                    break;
            }
        }
        return map;
    }

    public JSONObject getCaseInfoById(String id) {
        JSONObject list = caseDao.getCaseInfoById(id);
        return CommonUtil.successJson(list);
    }

    public JSONArray replaceVariable(JSONObject obj, JSONArray steps) {
        // 替换场景变量
        List<String> variableList = StringTools.getStringList(steps.toString(), "\\$\\{([^}]*)\\}");
        obj.put("variableList", variableList);
        if (variableList.size() > 0) {
            List<JSONObject> variableLists = caseDao.getVariableList(obj);
            steps = StringTools.replaceArray("\\$\\{(", "\\$*)\\}", steps, variableLists);
        }
        return steps;
    }

    public JSONArray replaceExpression(JSONObject obj, JSONArray steps) {
        // 替换元素表达式
        List<String> expressionList = StringTools.getStringList(steps.toString(), "\\$\\[([^}]*)\\]");
        obj.put("expressionList", expressionList);
        if (expressionList.size() > 0) {
            List<JSONObject> expressionLists = caseDao.getExpressionList(obj);
            steps = StringTools.replaceArray("\\$\\[(", "\\$*)\\]", steps, expressionLists);
        }
        return steps;
    }

    public JSONArray setCreationMode(JSONArray jsonArray, JSONArray step) {
        step.forEach(item -> {
            JSONObject jsonObject = JSONObject.parseObject(JSON.toJSONString(item));
            if (jsonObject.getString("parent_id").length() > 1) {
                jsonObject.put("creationMode", 1);
            } else {
                jsonObject.put("creationMode", 2);
            }
            jsonArray.add(jsonObject);
        });
        return jsonArray;
    }

    public String getImagePath(QiniuUtils qiniuUtils, String filePath) {
        try {
            File file = UrlToFIle.getFile(filePath);
            MultipartFile multipartFile = new MockMultipartFile("file", file.getName(), null, new FileInputStream(file));
            String imagePath = qiniuUtils.upload(multipartFile);
            return imagePath;
        } catch (Exception e) {
            e.printStackTrace();
            return e.toString();
        }
    }

    public JSONObject runCase(JSONObject obj) {
        ArrayList<HashMap<String, Object>> list = new ArrayList<>();
        HashMap<String, String> operationDictMap = operationDict();
        String filePath = obj.getString("filePath");
        JSONArray step = obj.getJSONArray("steps");
        // 获取所有用例的id
        JSONArray caseIdJSONArray = StringTools.caseIdRecursion(new JSONArray(), step);
        if (!caseIdJSONArray.contains(obj.getString("id"))) {
            caseIdJSONArray.add(obj.getString("id"));
        }
        log.info("caseIdList = " + caseIdJSONArray);
        obj.put("caseIds", caseIdJSONArray);
        // 步骤递归，扁平化
        step = StringTools.stepRecursion(JSONArray.parseArray(JSON.toJSONString(step)));
        log.info("扁平化的step = " + step);
        // 设置每条步骤的创建方式
        step = setCreationMode(new JSONArray(), step);
        int nums = 1;
        // 替换表达式
        step = replaceExpression(obj, step);
        // 替换场景变量
        step = replaceVariable(obj, step);
        // 生成通用步骤形式
        step = StringTools.getNewSteps(step);
        log.info("运行前的step = " + step);
        InvokeFunction instance = new InvokeFunction();
        BrowserOpt browserOpt = new BrowserOpt();
        try {
            instance.runConfigInfo(instance, browserOpt, "execHeadless", obj.getIntValue("isHandless"));
            ArrayList<String> fieldList = new ArrayList<>();
            List<Map<String, String>> rowList = new ArrayList<>();
            Map<String, String> rowMap = new HashMap<>();
            rowList.add(rowMap);
            if (filePath != null & filePath != "") {
                fieldList = ExcelUtil.getFieldList(filePath);
                rowList = ExcelUtil.redExcel(filePath);
                nums = rowList.size();
            }
            nums = nums == 0 ? 1 : nums;
            for (int n = 0; n < nums; n++) {
                HashMap<String, Object> resultMap = new HashMap<String, Object>();
                if (nums > 1) {
                    rowMap = rowList.get(n);
                }
                // 替换文件上传的变量，文件内容0行或没有变量 不替换处理
                JSONArray steps;
                if (fieldList.size() > 0 & rowMap.keySet().size() > 0) {
                    steps = StringTools.replaceArray("\\$\\{(", "\\$*)\\}", step, fieldList, rowMap);
                } else {
                    steps = step;
                }
                int successNum = 0;
                int failNum = 0;
                SimpleDateFormat ft = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
                long topTime = System.currentTimeMillis();
                resultMap.put("topTime", ft.format(new Date()));
                for (int i = 0; i < steps.size(); i++) {
                    if (!steps.getJSONObject(i).getString("status").equals("1")) {
                        continue;
                    }
                    JSONArray optElement = steps.getJSONObject(i).getJSONArray("optElement");
                    JSONArray optData = steps.getJSONObject(i).getJSONArray("optData");
                    optElement.addAll(optData);
                    //初始时间
                    long startTime = System.currentTimeMillis();
                    steps.getJSONObject(i).put("startTime", ft.format(new Date()));
                    String res = instance.runInstanceFunction(browserOpt, steps.getJSONObject(i).getString("func"), optElement, steps.getJSONObject(i).getString("stepName"));
                    if (steps.getJSONObject(i).getString("func").contains("Screenshot")) {
                        steps.getJSONObject(i).put("imagePath", getImagePath(qiniuUtils, res));
                        res = "success";
                    }
                    //结束时间
                    long endTime = System.currentTimeMillis();
                    steps.getJSONObject(i).put("time", endTime - startTime);
                    steps.getJSONObject(i).put("endTime", ft.format(new Date()));
                    steps.getJSONObject(i).put("result", res);
                    steps.getJSONObject(i).put("operateType", operationDictMap.get(steps.getJSONObject(i).getString("func")));
                    // 步骤执行失败 并且 步骤运行的方法不是execScreenshot
                    if (!res.equals("success") & !steps.getJSONObject(i).getString("func").equals("execScreenshot")) {
                        failNum += 1;
                        if (obj.getIntValue("isShot") == 1) {
                            JSONArray jsonArray = new JSONArray();
                            jsonArray.add(steps.getJSONObject(i).getString("stepName") + "-" + endTime);
                            instance.runInstanceFunction(browserOpt, "execScreenshot", jsonArray, "失败截图");
                            String localFilePath = QiniuUtils.getFileUrl() + steps.getJSONObject(i).getString("stepName") + "-" + endTime + ".png"; // build/reports/tests下的文件
                            steps.getJSONObject(i).put("imagePath", getImagePath(qiniuUtils, localFilePath));
                        }
                        if (obj.getIntValue("isContinueRun") == 0) {
                            break;
                        }
                    } else {
                        successNum += 1;
                    }
                }
                resultMap.put("endTime", ft.format(new Date()));
                resultMap.put("runTime", System.currentTimeMillis() - topTime);
                ArrayList<Object> chartList = new ArrayList<>();
                String[] statusArray = {"成功", "失败", "跳过"};
                int[] valueArray = {successNum, failNum, steps.size() - failNum - successNum};
                for (int i = 0; i < statusArray.length; i++) {
                    HashMap<String, Object> map = new HashMap<String, Object>();
                    map.put("name", statusArray[i]);
                    map.put("value", valueArray[i]);
                    chartList.add(map);
                }
                resultMap.put("chartList", chartList);
                resultMap.put("steps", steps);
                resultMap.put("level", obj.getString("level"));
                resultMap.put("isHandless", obj.getInteger("isHandless"));
                resultMap.put("isEndClose", obj.getInteger("isEndClose"));
                resultMap.put("isContinueRun", obj.getInteger("isContinueRun"));
                resultMap.put("isShot", obj.getInteger("isShot"));
                resultMap.put("name", obj.getString("name") + "-" + ft.format(new Date()));
                resultMap.put("valueArray", valueArray);
                resultMap.put("type", 2);
                // 用例计划记录表-记录执行结果
                if (obj.getString("id") != null) {
                    JSONObject jsonObject = new JSONObject();
                    jsonObject.put("casePlanId", obj.getString("id"));
                    jsonObject.put("casePlanName", obj.getString("name") + " - " + ft.format(new Date()));
                    jsonObject.put("type", 2);
                    jsonObject.put("result", failNum > 0 ? 2 : 1);
                    jsonObject.put("createUser", obj.getString("createUser"));
                    jsonObject.put("projectId", obj.getString("projectId"));
                    jsonObject.put("report", JSON.toJSONString(resultMap));
                    jsonObject.put("driveTime", filePath != null ? filePath.equals("") ? 0 : n + 1 : 0);
                    recordDao.addRecord(jsonObject);
                }
                list.add(resultMap);
            }
            instance.runConfigInfo(instance, browserOpt, "execCloseWindow", obj.getIntValue("isEndClose"));
            return CommonUtil.successJson(list);
        } catch (Exception e) {
            e.printStackTrace();
            log.error(e.toString());
            return CommonUtil.errorJson(e.getMessage());
        }
    }

    public void downloadTemplate(JSONObject obj, HttpServletResponse response) throws Exception {
        // 告诉浏览器用什么软件可以打开此文件
        String fileName = obj.getString("fileName");
        String sheetName = obj.getString("sheetName");
        String headerName = obj.getString("headerName");
        response.setHeader("content-Type", "application/vnd.ms-excel");
        // 下载文件的默认名称
        response.setHeader("Content-disposition", "attachment;fileName=" + fileName + ".xlsx");
        response.setHeader("FileName", URLEncoder.encode(fileName, "utf-8"));
        response.setHeader("Access-Control-Expose-Headers", "FileName");
        response.setContentType("application/octet-stream;charset=utf-8");
        OutputStream outPutStream = response.getOutputStream();
        List<Object[]> list = new ArrayList<>();
        String[] rowName = {headerName + "1", headerName + "2"};
        ExportExcel exportExcel = new ExportExcel(sheetName, rowName, list);
        exportExcel.export(outPutStream);
        outPutStream.flush();
        outPutStream.close();
    }

}

